/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
 */
package org.olap4j.driver.xmla;

import org.olap4j.*;
import org.olap4j.impl.Olap4jUtil;
import org.olap4j.metadata.*;
import org.olap4j.type.*;

import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.*;
import java.sql.Date;
import java.util.*;

/**
 * Implementation of {@link org.olap4j.PreparedOlapStatement} for XML/A providers.
 * 
 * <p>
 * This class has sub-classes which implement JDBC 3.0 and JDBC 4.0 APIs; it is instantiated using
 * {@link Factory#newPreparedStatement}.
 * </p>
 * 
 * @author jhyde
 * @since Jun 12, 2007
 */
abstract class XmlaOlap4jPreparedStatement extends XmlaOlap4jStatement implements PreparedOlapStatement,
		OlapParameterMetaData {
	final XmlaOlap4jCellSetMetaData cellSetMetaData;
	private final String mdx;

	/**
	 * Creates an XmlaOlap4jPreparedStatement.
	 * 
	 * @param olap4jConnection
	 *            Connection
	 * @param mdx
	 *            MDX query string
	 * @throws OlapException
	 *             on error
	 */
	XmlaOlap4jPreparedStatement(XmlaOlap4jConnection olap4jConnection, String mdx) throws OlapException {
		super(olap4jConnection);

		// Execute a statement and steal its metadata.
		final OlapStatement statement = olap4jConnection.createStatement();
		try {
			final CellSet cellSet = statement.executeOlapQuery(mdx);
			final XmlaOlap4jCellSetMetaData cellSetMetaData1 = (XmlaOlap4jCellSetMetaData) cellSet.getMetaData();
			this.cellSetMetaData = cellSetMetaData1.cloneFor(this);
			cellSet.close();
			statement.close();
		} catch (SQLException e) {
			throw getHelper().createException("Error while preparing statement '" + mdx + "'", e);
		}

		this.mdx = mdx;
	}

	/**
	 * Returns the error-handler.
	 * 
	 * @return Error handler
	 */
	private final XmlaHelper getHelper() {
		return olap4jConnection.helper;
	}

	// override OlapStatement

	public CellSet executeOlapQuery(String mdx) throws OlapException {
		return super.executeOlapQuery(mdx);
	}

	// implement PreparedOlapStatement

	public CellSet executeQuery() throws OlapException {
		return executeOlapQuery(mdx);
	}

	public OlapParameterMetaData getParameterMetaData() throws OlapException {
		return this;
	}

	public Cube getCube() {
		return cellSetMetaData.cube;
	}

	// implement PreparedStatement

	public int executeUpdate() throws SQLException {
		throw new UnsupportedOperationException();
	}

	public void setNull(int parameterIndex, int sqlType) throws SQLException {
		getParameter(parameterIndex).setValue(null);
	}

	public void setBoolean(int parameterIndex, boolean x) throws SQLException {
		getParameter(parameterIndex).setValue(x);
	}

	public void setByte(int parameterIndex, byte x) throws SQLException {
		getParameter(parameterIndex).setValue(x);
	}

	public void setShort(int parameterIndex, short x) throws SQLException {
		getParameter(parameterIndex).setValue(x);
	}

	public void setInt(int parameterIndex, int x) throws SQLException {
		getParameter(parameterIndex).setValue(x);
	}

	public void setLong(int parameterIndex, long x) throws SQLException {
		getParameter(parameterIndex).setValue(x);
	}

	public void setFloat(int parameterIndex, float x) throws SQLException {
		getParameter(parameterIndex).setValue(x);
	}

	public void setDouble(int parameterIndex, double x) throws SQLException {
		getParameter(parameterIndex).setValue(x);
	}

	public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
		getParameter(parameterIndex).setValue(x);
	}

	public void setString(int parameterIndex, String x) throws SQLException {
		getParameter(parameterIndex).setValue(x);
	}

	public void setBytes(int parameterIndex, byte x[]) throws SQLException {
		getParameter(parameterIndex).setValue(x);
	}

	public void setDate(int parameterIndex, Date x) throws SQLException {
		getParameter(parameterIndex).setValue(x);
	}

	public void setTime(int parameterIndex, Time x) throws SQLException {
		getParameter(parameterIndex).setValue(x);
	}

	public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
		getParameter(parameterIndex).setValue(x);
	}

	public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
		getParameter(parameterIndex).setValue(x);
	}

	public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
		getParameter(parameterIndex).setValue(x);
	}

	public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
		getParameter(parameterIndex).setValue(x);
	}

	public void unset(int parameterIndex) throws SQLException {
		getParameter(parameterIndex).unset();
	}

	public boolean isSet(int parameterIndex) throws SQLException {
		return getParameter(parameterIndex).isSet();
	}

	public void clearParameters() throws SQLException {
		throw new UnsupportedOperationException();
	}

	public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
		getParameter(parameterIndex).setValue(x);
	}

	public void setObject(int parameterIndex, Object x) throws SQLException {
		final Parameter parameter = getParameter(parameterIndex);
		parameter.setValue(x);
	}

	public boolean execute() throws SQLException {
		throw new UnsupportedOperationException();
	}

	public void addBatch() throws SQLException {
		throw new UnsupportedOperationException();
	}

	public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
		throw new UnsupportedOperationException();
	}

	public void setRef(int parameterIndex, Ref x) throws SQLException {
		throw new UnsupportedOperationException();
	}

	public void setBlob(int parameterIndex, Blob x) throws SQLException {
		throw new UnsupportedOperationException();
	}

	public void setClob(int parameterIndex, Clob x) throws SQLException {
		throw new UnsupportedOperationException();
	}

	public void setArray(int parameterIndex, Array x) throws SQLException {
		throw new UnsupportedOperationException();
	}

	public CellSetMetaData getMetaData() {
		return cellSetMetaData;
	}

	public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
		throw new UnsupportedOperationException();
	}

	public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
		throw new UnsupportedOperationException();
	}

	public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
		throw new UnsupportedOperationException();
	}

	public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
		throw new UnsupportedOperationException();
	}

	public void setURL(int parameterIndex, URL x) throws SQLException {
		throw new UnsupportedOperationException();
	}

	public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException {
		throw new UnsupportedOperationException();
	}

	// implement OlapParameterMetaData

	public String getParameterName(int param) throws OlapException {
		Parameter paramDef = getParameter(param);
		return paramDef.getName();
	}

	private Parameter getParameter(int param) throws OlapException {
		final List<Parameter> parameters = getParameters();
		if (param < 1 || param > parameters.size()) {
			throw getHelper().createException("parameter ordinal " + param + " out of range");
		}
		return parameters.get(param - 1);
	}

	private List<Parameter> getParameters() {
		// XMLA statements do not have parameters yet
		return Collections.emptyList();
	}

	public Type getParameterOlapType(int param) throws OlapException {
		throw Olap4jUtil.needToImplement(this);
	}

	public int getParameterCount() {
		return getParameters().size();
	}

	public int isNullable(int param) throws SQLException {
		return ParameterMetaData.parameterNullableUnknown;
	}

	public boolean isSigned(int param) throws SQLException {
		final Type type = getParameterOlapType(param);
		return type instanceof NumericType;
	}

	public int getPrecision(int param) throws SQLException {
		final Type type = getParameterOlapType(param);
		if (type instanceof NumericType) {
			return 0; // precision not applicable
		}
		if (type instanceof StringType) {
			return Integer.MAX_VALUE;
		}
		return 0;
	}

	public int getScale(int param) throws SQLException {
		return 0; // scale not applicable
	}

	public int getParameterType(int param) throws SQLException {
		final Type type = getParameterOlapType(param);
		if (type instanceof NumericType) {
			return Types.NUMERIC;
		} else if (type instanceof StringType) {
			return Types.VARCHAR;
		} else if (type instanceof NullType) {
			return Types.NULL;
		} else {
			return Types.OTHER;
		}
	}

	public String getParameterTypeName(int param) throws SQLException {
		final Type type = getParameterOlapType(param);
		return type.toString();
	}

	public String getParameterClassName(int param) throws SQLException {
		final Type type = getParameterOlapType(param);
		return foo(new XmlaOlap4jPreparedStatement.TypeHelper<Class<?>>() {
			public Class<?> booleanType(BooleanType type) {
				return Boolean.class;
			}

			public Class<Cube> cubeType(CubeType cubeType) {
				return Cube.class;
			}

			public Class<Number> decimalType(DecimalType decimalType) {
				return Number.class;
			}

			public Class<Dimension> dimensionType(DimensionType dimensionType) {
				return Dimension.class;
			}

			public Class<Hierarchy> hierarchyType(HierarchyType hierarchyType) {
				return Hierarchy.class;
			}

			public Class<Level> levelType(LevelType levelType) {
				return Level.class;
			}

			public Class<Member> memberType(MemberType memberType) {
				return Member.class;
			}

			public Class<Void> nullType(NullType nullType) {
				return Void.class;
			}

			public Class<Number> numericType(NumericType numericType) {
				return Number.class;
			}

			public Class<Iterable> setType(SetType setType) {
				return Iterable.class;
			}

			public Class<String> stringType(StringType stringType) {
				return String.class;
			}

			public Class<Member[]> tupleType(TupleType tupleType) {
				return Member[].class;
			}

			public Class<?> symbolType(SymbolType symbolType) {
				// parameters cannot be of this type
				throw new UnsupportedOperationException();
			}
		}, type).getName();
	}

	public int getParameterMode(int param) throws SQLException {
		throw Olap4jUtil.needToImplement(this);
	}

	// Helper classes

	private interface TypeHelper<T> {
		T booleanType(BooleanType type);

		T cubeType(CubeType cubeType);

		T decimalType(DecimalType decimalType);

		T dimensionType(DimensionType dimensionType);

		T hierarchyType(HierarchyType hierarchyType);

		T levelType(LevelType levelType);

		T memberType(MemberType memberType);

		T nullType(NullType nullType);

		T numericType(NumericType numericType);

		T setType(SetType setType);

		T stringType(StringType stringType);

		T tupleType(TupleType tupleType);

		T symbolType(SymbolType symbolType);
	}

	<T> T foo(XmlaOlap4jPreparedStatement.TypeHelper<T> helper, Type type) {
		if (type instanceof BooleanType) {
			return helper.booleanType((BooleanType) type);
		} else if (type instanceof CubeType) {
			return helper.cubeType((CubeType) type);
		} else if (type instanceof DecimalType) {
			return helper.decimalType((DecimalType) type);
		} else if (type instanceof DimensionType) {
			return helper.dimensionType((DimensionType) type);
		} else if (type instanceof HierarchyType) {
			return helper.hierarchyType((HierarchyType) type);
		} else if (type instanceof LevelType) {
			return helper.levelType((LevelType) type);
		} else if (type instanceof MemberType) {
			return helper.memberType((MemberType) type);
		} else if (type instanceof NullType) {
			return helper.nullType((NullType) type);
		} else if (type instanceof NumericType) {
			return helper.numericType((NumericType) type);
		} else if (type instanceof SetType) {
			return helper.setType((SetType) type);
		} else if (type instanceof StringType) {
			return helper.stringType((StringType) type);
		} else if (type instanceof TupleType) {
			return helper.tupleType((TupleType) type);
		} else if (type instanceof SymbolType) {
			return helper.symbolType((SymbolType) type);
		} else {
			throw new UnsupportedOperationException();
		}
	}

	private interface Parameter {
		String getName();

		void setValue(Object o);

		void unset();

		boolean isSet();
	}
}

// End XmlaOlap4jPreparedStatement.java
